#pragma once

#include <entt/entt.hpp>
#include <functional>
#include <optional>

#include "./bus.hpp"
#include "./event.hpp"
#include "./query.hpp"
#include "./resource.hpp"
#include "./utils.hpp"

namespace qbe::ecs {

class Query;

template <class T>
struct is_query : public std::false_type {};
template <>
struct is_query<Query> : public std::true_type {};
// template <>
// struct is_query<Query&> : public std::true_type {};
// template <>
// struct is_query<const Query&> : public std::true_type {};

class Commands;

template <class T>
struct is_commands : public std::false_type {};
template <>
struct is_commands<Commands> : public std::true_type {};

class Scheduler;

template <class T>
struct is_world : public std::false_type {};
template <>
struct is_world<World&> : public std::true_type {};
template <>
struct is_world<const World&> : public std::true_type {};

template <class T>
struct is_registry : public std::false_type {};

template <>
struct is_registry<entt::registry&> : public std::true_type {};

template <>
struct is_registry<const entt::registry&> : public std::true_type {};

class World {
  using value_type = World;
  friend class Scheduler;

 private:
  ResourceManager _resource_manager;
  entt::registry _registry;
  Bus _bus;

 public:
  World() { insert_resource(&_bus); }

  void clear_all() {
    _registry.clear();
    _resource_manager.clear_resources();
  }
  void clear_resources() { _resource_manager.clear_resources(); }

  Bus& get_bus() { return _bus; }

  /*
  **
  ** Components
  **
  */

  template <typename... Args>
  entt::entity spawn(Args&&... args) {
    auto e = _registry.create();
    (_registry.emplace<Args>(e, args), ...);
    return e;
  }

  entt::registry& get_registry() { return _registry; }

  /*
  **
  ** Events
  **
  */
  template <typename E>
  void init_event() {
    init_resource<Event<E>>();
  }

  /*
  **
  ** RESOURCES
  **
  */

  /// emplace a resource to the world by calling the
  template <typename R, typename... Params>
  void init_resource(Params&&... p) {
    _resource_manager.insert_resource(R(p...));
  }

  template <typename R>
  void init_resource() {
    _resource_manager.init_resource<R>();
  }

  /// insert a resource to the world
  template <typename R>
  void insert_resource(const R& value) {
    _resource_manager.insert_resource(value);
  }

  template <typename... Args>
  void insert_resources(Args&&... args) {
    (insert_resource(args), ...);
  }

  /// remove a resource from this world
  template <typename R>
  std::optional<R> remove_resource() {
    return _resource_manager.remove_resource<R>();
  }

  /// true if the world holds this Resource
  template <typename R>
  bool contains_resource() {
    auto type = typeid(R).name();
    return _resource_manager.contains_resource<R>();
  }

  /// access a given Resource
  template <typename R>
  R& resource() {
    return _resource_manager.resource<R>();
  }

  /// access a given Resource
  template <typename R>
  const R& resource() const {
    return _resource_manager.resource<R>();
  }

  /// access a given Resource, or None if it not in this world
  template <typename R>
  std::optional<std::reference_wrapper<R>> get_resource() {
    return _resource_manager.get_resource<R>();
  }

  /*
  **
  ** QUERIES
  **
  */

  template <typename W, typename std::enable_if_t<is_world<W>::value, bool> = true>
  World& get_super(size_t) {
    return *this;
  }

  // template <typename Q, typename std::enable_if_t<is_query<Q>::value, bool> = true>
  // Q get_super(size_t) {
  //   return Q::query(*this);
  // }

  template <typename Q, typename std::enable_if_t<is_registry<Q>::value, bool> = true>
  Q& get_super(size_t) {
    return _registry;
  }

  template <typename R, typename std::enable_if_t<is_event_reader<R>::value, bool> = true>
  R get_super(size_t) {
    if (!contains_resource<Event<typename R::value_type>>())
      throw(std::runtime_error("Ecs: Try to use EventReader on an unregisted event!"));

    return EventReader(resource<Event<typename R::value_type>>());
  }

  template <typename W, typename std::enable_if_t<is_event_writer<W>::value, bool> = true>
  W get_super(size_t system_id) {
    if (!contains_resource<Event<typename W::value_type>>())
      throw(std::runtime_error("Ecs: Try to use EventWriter on an unregisted event!"));

    auto& res = resource<Event<typename W::value_type>>();

    if (res.event_queue.empty())
      return EventWriter(res, system_id);

    for (int i = res.event_queue.size() - 1; i >= 0; i--)
      if (std::get<1>(res.event_queue.at(i)) == system_id)
        res.event_queue.erase(res.event_queue.begin() + i);
    return EventWriter(res, system_id);
  }

  template <typename R, typename std::enable_if_t<is_resource<R>::value, bool> = true>
  R get_super(size_t) {
    return _resource_manager.get<typename R::value>();
  }

  template <typename Q, typename std::enable_if_t<is_query<Q>::value, bool> = true>
  Q get_super(size_t) {
    return Query(*this);
  };

  template <typename B, typename std::enable_if_t<is_bus<B>::value, bool> = true>
  B get_super(size_t) {
    return _bus;
  };

  template <typename C, typename std::enable_if_t<is_commands<C>::value, bool> = true>
  C get_super(size_t) {
    return Commands(*this);
  };

  template <class R, class... Args>
  R run_system(R (&&func)(Args...)) {
    static_assert(all(Or<is_world<Args>, is_query<Args>, is_bus<Args>, is_commands<Args>, is_registry<Args>, is_resource<Args>, is_event_reader<Args>,
                         is_event_writer<Args>>()...),
                  "type must be reference to world, commands or resource");
    auto sys = [&func, this]() mutable -> R {
      return func(get_super<Args>(0)...);
    };
    return sys();
  }

  template <class R, class... Args>
  R run_system(std::function<R(Args...)> func) {
    static_assert(all(Or<is_world<Args>, is_query<Args>, is_bus<Args>, is_commands<Args>, is_registry<Args>, is_resource<Args>, is_event_reader<Args>,
                         is_event_writer<Args>>()...),
                  "type must be reference to world, commands or resource");
    auto sys = [&func, this]() mutable -> R {
      return func(get_super<Args>(0)...);
    };
    return sys();
  }

  template <class GivenArgs, class R, class... Args>
  R run_system_with(R (&&func)(GivenArgs, Args...), GivenArgs given) {
    static_assert(all(Or<is_world<Args>, is_query<Args>, is_bus<Args>, is_commands<Args>, is_registry<Args>, is_resource<Args>, is_event_reader<Args>,
                         is_event_writer<Args>>()...),
                  "type must be reference to world, commands or resource");
    auto sys = [&func, this, given]() mutable -> R {
      return func(given, get_super<Args>(0)...);
    };
    return sys();
  }

  template <class GivenArgs, class R, class... Args>
  R run_system_with(std::function<R(GivenArgs, Args...)> func, GivenArgs given) {
    static_assert(all(Or<is_world<Args>, is_query<Args>, is_bus<Args>, is_commands<Args>, is_registry<Args>, is_resource<Args>, is_event_reader<Args>,
                         is_event_writer<Args>>()...),
                  "type must be reference to world, commands or resource");
    auto sys = [&func, this, given]() mutable -> R {
      return func(given, get_super<Args>(0)...);
    };
    return sys();
  }
};

}  // namespace qbe::ecs
